package org.example.bean;


import lombok.Data;
import org.example.util.LazyNodePool;

import java.util.*;

/**
 * @Desc:游戏地图信息
 * @Author: yanghongjun
 * @Date: 2024/5/6
 **/
@Data
public class GameHashMap {

    //偏移量，用来转换坐标
    private int offsetX;
    private int offsetY;
    //地图格子长宽
    private int weight;
    private int height;
    //阻挡哈希稀疏矩阵
    private final Map<Integer, Set<Integer>> blocks;
    //8个方向
    private final int[][] moves = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1}};
    //节点对象池
    private LazyNodePool lazyNodePool;

    /**
     * @param minX
     * @param maxX
     * @param minY
     * @param maxY
     */
    public GameHashMap(int minX, int maxX, int minY, int maxY) {
        weight = Math.abs(minX) + maxX;
        height = Math.abs(minY) + maxY;
        //找到x和y方向上的最小值和偏移量
        offsetX = Math.abs(minX);
        offsetY = Math.abs(minY);
        //阻挡哈希稀疏矩阵
        this.blocks = new HashMap<>();
        //初始化对象池
        lazyNodePool = new LazyNodePool(10000, weight * height);
    }

    /**
     * 添加hash阻挡
     *
     * @param x
     * @param y
     */
    public void addBlock(int x, int y) {
        if (x >= 0 && x < weight && y >= 0 && y < height) {
            // 获取x行对应的列集合（如果不存在则创建）
            Set<Integer> columns = blocks.computeIfAbsent(x, k -> new HashSet<>());
            // 添加y列到集合中
            columns.add(y);
        }
    }

    /**
     * 移除hash阻挡
     *
     * @param x
     * @param y
     */
    public void removeBlock(int x, int y) {
        if (x >= 0 && x < weight && y >= 0 && y < height) {
            if (blocks.containsKey(x)) {
                Set<Integer> columns = blocks.get(x);
                columns.remove(y);
            }
        }
    }

    /**
     * 检查是否为阻挡
     *
     * @param x
     * @param y
     * @return
     */
    public boolean isBlocked(int x, int y) {
        // 检查x行是否存在，并且y列是否在该行的集合中
        Set<Integer> columns = blocks.get(x);
        return columns != null && columns.contains(y);
    }


    /**
     * 将游戏坐标转换为LazyNode。
     *
     * @param gameX 游戏内的X坐标
     * @param gameY 游戏内的Y坐标
     * @return 一个包含地图数组索引的整数数组，第一个元素是X索引，第二个元素是Y索引
     */
    public LazyNode gameToMapIndex(float gameX, float gameY) {
        float mapX = gameX + offsetX;
        float mapY = gameY + offsetY;
        return new LazyNode((int) (mapX), (int) (mapY));
    }

    /**
     * 将地图LazyNode转换回游戏内的坐标。
     *
     * @param mapX 地图数组的X索引
     * @param mapY 地图数组的Y索引
     * @return 一个包含游戏内坐标的整数数组，第一个元素是X坐标，第二个元素是Y坐标
     */
    public JsonGamePos mapToGameCoords(int mapX, int mapY) {
        int gameX = mapX - offsetX;
        int gameY = mapY - offsetY;
        return new JsonGamePos((float) gameX, (float) gameY);
    }

    /**
     * 在地图上添加一个圆形阻挡区域。
     *
     * @param centerX 圆形阻挡的中心点X坐标（游戏坐标）
     * @param centerY 圆形阻挡的中心点Y坐标（游戏坐标）
     * @param radius  圆形阻挡的半径
     */
    public void addCircularObstacle(float centerX, float centerY, int radius) {
        // 转换为地图坐标
        int mapCenterX = (int) (centerX + offsetX);
        int mapCenterY = (int) (centerY + offsetY);
        // 遍历圆形区域内的每个点，检查并设置阻挡
        for (int x = mapCenterX - radius; x <= (mapCenterX + radius); x++) {
            for (int y = (int) (mapCenterY - radius); y <= (mapCenterY + radius); y++) {
                // 判断点 (x, y) 是否在圆形区域内，并在区域内设置阻挡
                if (isInsideCircle(mapCenterX, mapCenterY, x, y, radius)) {
                    // 确保索引在地图范围内
                    if (x >= 0 && x < weight && y >= 0 && y < height) {
                        // 设置为阻挡
                        addBlock(x, y);
                    }
                }
            }
        }
    }

    /**
     * 判断一个点是否在以 (cx, cy) 为中心，r 为半径的圆内。
     *
     * @param cx 圆的中心点X坐标
     * @param cy 圆的中心点Y坐标
     * @param x  要检查的点的X坐标
     * @param y  要检查的点的Y坐标
     * @param r  圆的半径
     * @return 如果点在圆内，则返回 true；否则返回 false
     */
    public boolean isInsideCircle(float cx, float cy, float x, float y, int r) {
        float distance = (float) Math.sqrt(Math.pow(cx - x, 2) + Math.pow(cy - y, 2));
        return distance <= r;
    }

    /**
     * 从地图上移除一个圆形阻挡区域。
     *
     * @param centerX 圆形阻挡的中心点X坐标（游戏坐标）
     * @param centerY 圆形阻挡的中心点Y坐标（游戏坐标）
     * @param radius  圆形阻挡的半径
     */
    public void removeCircularObstacle(float centerX, float centerY, int radius) {
        // 转换为地图坐标
        float mapCenterX = centerX + offsetX;
        float mapCenterY = centerY + offsetY;
        // 遍历圆形区域内的每个点，清除阻挡
        for (int x = (int) (mapCenterX - radius); x <= (mapCenterX + radius); x++) {
            for (int y = (int) (mapCenterY - radius); y <= (mapCenterY + radius); y++) {
                // 判断点 (x, y) 是否在圆形区域内，并在区域内清除阻挡
                if (isInsideCircle(mapCenterX, mapCenterY, x, y, radius)) {
                    // 确保索引在地图范围内
                    if (x >= 0 && x < weight && y >= 0 && y < height) {
                        // 清除阻挡
                        removeBlock(x, y);
                    }
                }
            }
        }
    }

    /**
     * 检查两个节点是否可以直线相连而不穿过障碍物。
     *
     * @param x1 起点x
     * @param y1 起点y
     * @param x2 终点x
     * @param y2 终点y
     * @return 如果两点之间可以直线相连并且没有障碍物则返回true，否则返回false
     */
    public boolean canConnectStraight(int x1, int y1, int x2, int y2) {
        int dx = Math.abs(x2 - x1);
        int dy = Math.abs(y2 - y1);
        int sx = (x1 < x2) ? 1 : -1;
        int sy = (y1 < y2) ? 1 : -1;
        int err = dx - dy;
        while (true) {
            if (isInBounds(x1, y1) && isBlocked(x1, y1)) {
                // 路径上有障碍物
                return false;
            }
            if (x1 == x2 && y1 == y2) {
                break; // 到达终点
            }
            int e2 = err * 2;
            if (e2 > -dy) {
                err -= dy;
                x1 += sx;
            }
            if (e2 < dx) {
                err += dx;
                y1 += sy;
            }
        }
        // 路径上无障碍物
        return true;
    }

    /**
     * 判断给定坐标是否在网格地图范围内且不是障碍物。
     *
     * @param x 横坐标
     * @param y 纵坐标
     * @return 若坐标在网格范围内且不是障碍物则返回 true，否则返回 false
     */
    public boolean isValid(int x, int y) {
        return x >= 0 && x < weight && y >= 0 && y < height && !isBlocked(x, y);
    }

    /**
     * 检查给定的坐标是否在网格的边界内。
     *
     * @param x 坐标x
     * @param y 坐标y
     * @return 如果坐标在网格内则返回true，否则返回false
     */
    public boolean isInBounds(int x, int y) {
        return x >= 0 && x < weight && y >= 0 && y < height;
    }

    /**
     * 优化路径，通过去除不必要的节点来简化路径。
     *
     * @param path 原始路径
     * @return 优化后的路径
     */
    public List<JsonGamePos> optimizePath(List<Cord> path) {
        if (path == null || path.isEmpty()) {
            return null;
        }

        List<JsonGamePos> optimizedPathPositions = new ArrayList<>();
        if (path.size() >= 3) {
            List<Cord> optimizedPath = new ArrayList<>();
            // 起点总是加入优化路径
            optimizedPath.add(path.get(0));
            Cord lastOptimizedNode = path.get(0);
            for (int i = 1; i < path.size() - 1; i++) {
                Cord currentNode = path.get(i);
                Cord nextNode = path.get(i + 1);
                // 检查当前节点是否可以与下一个节点直线相连而不穿过障碍物
                if (!canConnectStraight(lastOptimizedNode, nextNode)) {
                    // 如果不能直线相连，则添加当前节点到优化路径中
                    optimizedPath.add(currentNode);
                    lastOptimizedNode = currentNode;
                }
            }
            // 终点总是加入优化路径
            optimizedPath.add(path.get(path.size() - 1));

            // 将Cord路径转换为JsonGamePos列表
            for (Cord cord : optimizedPath) {
                optimizedPathPositions.add(mapToGameCoords(cord.getX(), cord.getY()));
            }
        } else {
            // 如果路径长度小于3，则直接转换并返回
            for (Cord cord : path) {
                optimizedPathPositions.add(mapToGameCoords(cord.getX(), cord.getY()));
            }
        }
        return optimizedPathPositions;
    }

    // 简化canConnectStraight方法，因为我们只关心是否可以直线相连
    private boolean canConnectStraight(Cord start, Cord end) {
        return canConnectStraight(start.getX(), start.getY(), end.getX(), end.getY());
    }

    /**
     * 获取给定节点的所有邻居节点。
     *
     * @param node 给定节点
     * @return 邻居节点列表
     */
    public List<LazyNode> getHashNeighbors(LazyNode node) {
        List<LazyNode> neighbors = new ArrayList<>();
        for (int i = 0; i < moves.length; i++) {
            int[] move = moves[i];
            int nextX = node.getX() + move[0];
            int nextY = node.getY() + move[1];
            if (nextX >= 0 && nextY >= 0 && nextX < weight && nextY < height && !isBlocked(nextX, nextY)) {
                //LazyNode acquire = lazyNodePool.acquire();
                LazyNode acquire = new LazyNode();
                acquire.setX(nextX);
                acquire.setY(nextY);
                acquire.setDirection(i + 1);
                neighbors.add(acquire);
            }
        }
        return neighbors;
    }

}
